/*
 * Copyright (c) 2011-2014, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package boofcv.alg.misc;

import boofcv.struct.image.*;

import java.util.Random;


/**
 * Basic image operations which have no place better to go.
 *
 * <p>DO NOT MODIFY: Generated by {@link boofcv.alg.misc.GenerateImageMiscOps}.</p>
 *
 * @author Peter Abeles
 */
public class ImageMiscOps {

	/**
	 * Copies a rectangular region from one image into another.<br>
	 * output[dstX:(dstX+width) , dstY:(dstY+height-1)] = input[srcX:(srcX+width) , srcY:(srcY+height-1)]
	 *
	 * @param srcX x-coordinate of corner in input image
	 * @param srcY y-coordinate of corner in input image
	 * @param dstX x-coordinate of corner in output image
	 * @param dstY y-coordinate of corner in output image
	 * @param width Width of region to be copied
	 * @param height Height of region to be copied
	 * @param input Input image
	 * @param output output image
	 */
	public static void copy( int srcX , int srcY , int dstX , int dstY , int width , int height ,
							 ImageInt8 input , ImageInt8 output ) {

		if( input.width < srcX+width || input.height < srcY+height )
			throw new IllegalArgumentException("Copy region must be contained input image");
		if( output.width < dstX+width || output.height < dstY+height )
			throw new IllegalArgumentException("Copy region must be contained output image");

		for (int y = 0; y < height; y++) {
			int indexSrc = input.startIndex + (srcY + y) * input.stride + srcX;
			int indexDst = output.startIndex + (dstY + y) * output.stride + dstX;

			for (int x = 0; x < width; x++) {
				output.data[indexDst++] = input.data[indexSrc++];
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(ImageInt8 input, int value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				input.data[index++] = (byte)value;
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(InterleavedI8 input, int value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			int end = index + input.width*input.numBands;
			for (; index < end; index++ ) {
				input.data[index] = (byte)value;
			}
		}
	}

	/**
	 * Fills the outside border with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 * @param radius Border width.   
	 */
	public static void fillBorder(ImageInt8 input, int value, int radius ) {

		// top and bottom
		for (int y = 0; y < radius; y++) {
			int indexTop = input.startIndex + y * input.stride;
			int indexBottom = input.startIndex + (input.height-y-1) * input.stride;
			for (int x = 0; x < input.width; x++) {
				input.data[indexTop++] = (byte)value;
				input.data[indexBottom++] = (byte)value;
			}
		}

		// left and right
		int h = input.height-radius;
		int indexStart = input.startIndex + radius*input.stride;
		for (int x = 0; x < radius; x++) {
			int indexLeft = indexStart + x;
			int indexRight = indexStart + input.width-1-x;
			for (int y = radius; y < h; y++) {
				input.data[indexLeft] = (byte)value;
				input.data[indexRight] = (byte)value;
				
				indexLeft += input.stride;
				indexRight += input.stride;
			}
		}
	}

	/**
	 * Draws a filled rectangle that is aligned along the image axis inside the image.
	 *
	 * @param img Image the rectangle is drawn in.  Modified
	 * @param value Value of the rectangle
	 * @param x0 Top left x-coordinate
	 * @param y0 Top left y-coordinate
	 * @param width Rectangle width
	 * @param height Rectangle height
	 */
	public static void fillRectangle(ImageInt8 img, int value, int x0, int y0, int width, int height) {
		int x1 = x0 + width;
		int y1 = y0 + height;

		for (int y = y0; y < y1; y++) {
			for (int x = x0; x < x1; x++) {
				if( img.isInBounds(x,y ))
					img.set(x, y, value);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(ImageInt8 img, Random rand , int min , int max) {
		int range = max-min;

		byte[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			for (int x = 0; x < img.width; x++) {
				data[index++] = (byte)(rand.nextInt(range)+min);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(InterleavedI8 img, Random rand , int min , int max) {
		int range = max-min;

		byte[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			int end = index + img.width*img.numBands;
			for (; index <  end; index++) {
				data[index] = (byte)(rand.nextInt(range)+min);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from a Gaussian distribution.  A user
	 * specified lower and upper bound is provided to ensure that the values are within a legal
	 * range.  A drawn value outside the allowed range will be set to the closest bound.
	 * 
	 * @param input Input image.  Modified.
	 * @param rand Random number generator
	 * @param mean Distribution's mean.
	 * @param sigma Distribution's standard deviation.
	 * @param lowerBound Lower bound of value clip
	 * @param upperBound Upper bound of value clip
	 */
	public static void fillGaussian(ImageInt8 input, Random rand , double mean , double sigma , int lowerBound , int upperBound ) {
		byte[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (int)(rand.nextGaussian()*sigma+mean);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				data[index++] = (byte)value;
			}
		}
	}

	/**
	 * Flips the image from top to bottom
	 */
	public static void flipVertical( ImageInt8 input ) {
		int h2 = input.height/2;

		for( int y = 0; y < h2; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = input.getStartIndex() + (input.height - y - 1) * input.getStride();

			int end = index1 + input.width;

			while( index1 < end ) {
				int tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2++] = (byte)tmp;
			}
		}
	}

	/**
	 * Flips the image from left to right
	 */
	public static void flipHorizontal( ImageInt8 input ) {
		int w2 = input.width/2;

		for( int y = 0; y < input.height; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = index1 + input.width-1;

			int end = index1 + w2;

			while( index1 < end ) {
				int tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2--] = (byte)tmp;
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the clockwise direction.
	 */
	public static void rotateCW( ImageInt8 input , ImageInt8 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int h = input.height-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(h-y,x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the counter-clockwise direction.
	 */
	public static void rotateCCW( ImageInt8 input , ImageInt8 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int w = input.width-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(y,w-x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Copies a rectangular region from one image into another.<br>
	 * output[dstX:(dstX+width) , dstY:(dstY+height-1)] = input[srcX:(srcX+width) , srcY:(srcY+height-1)]
	 *
	 * @param srcX x-coordinate of corner in input image
	 * @param srcY y-coordinate of corner in input image
	 * @param dstX x-coordinate of corner in output image
	 * @param dstY y-coordinate of corner in output image
	 * @param width Width of region to be copied
	 * @param height Height of region to be copied
	 * @param input Input image
	 * @param output output image
	 */
	public static void copy( int srcX , int srcY , int dstX , int dstY , int width , int height ,
							 ImageInt16 input , ImageInt16 output ) {

		if( input.width < srcX+width || input.height < srcY+height )
			throw new IllegalArgumentException("Copy region must be contained input image");
		if( output.width < dstX+width || output.height < dstY+height )
			throw new IllegalArgumentException("Copy region must be contained output image");

		for (int y = 0; y < height; y++) {
			int indexSrc = input.startIndex + (srcY + y) * input.stride + srcX;
			int indexDst = output.startIndex + (dstY + y) * output.stride + dstX;

			for (int x = 0; x < width; x++) {
				output.data[indexDst++] = input.data[indexSrc++];
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(ImageInt16 input, int value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				input.data[index++] = (short)value;
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(InterleavedI16 input, int value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			int end = index + input.width*input.numBands;
			for (; index < end; index++ ) {
				input.data[index] = (short)value;
			}
		}
	}

	/**
	 * Fills the outside border with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 * @param radius Border width.   
	 */
	public static void fillBorder(ImageInt16 input, int value, int radius ) {

		// top and bottom
		for (int y = 0; y < radius; y++) {
			int indexTop = input.startIndex + y * input.stride;
			int indexBottom = input.startIndex + (input.height-y-1) * input.stride;
			for (int x = 0; x < input.width; x++) {
				input.data[indexTop++] = (short)value;
				input.data[indexBottom++] = (short)value;
			}
		}

		// left and right
		int h = input.height-radius;
		int indexStart = input.startIndex + radius*input.stride;
		for (int x = 0; x < radius; x++) {
			int indexLeft = indexStart + x;
			int indexRight = indexStart + input.width-1-x;
			for (int y = radius; y < h; y++) {
				input.data[indexLeft] = (short)value;
				input.data[indexRight] = (short)value;
				
				indexLeft += input.stride;
				indexRight += input.stride;
			}
		}
	}

	/**
	 * Draws a filled rectangle that is aligned along the image axis inside the image.
	 *
	 * @param img Image the rectangle is drawn in.  Modified
	 * @param value Value of the rectangle
	 * @param x0 Top left x-coordinate
	 * @param y0 Top left y-coordinate
	 * @param width Rectangle width
	 * @param height Rectangle height
	 */
	public static void fillRectangle(ImageInt16 img, int value, int x0, int y0, int width, int height) {
		int x1 = x0 + width;
		int y1 = y0 + height;

		for (int y = y0; y < y1; y++) {
			for (int x = x0; x < x1; x++) {
				if( img.isInBounds(x,y ))
					img.set(x, y, value);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(ImageInt16 img, Random rand , int min , int max) {
		int range = max-min;

		short[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			for (int x = 0; x < img.width; x++) {
				data[index++] = (short)(rand.nextInt(range)+min);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(InterleavedI16 img, Random rand , int min , int max) {
		int range = max-min;

		short[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			int end = index + img.width*img.numBands;
			for (; index <  end; index++) {
				data[index] = (short)(rand.nextInt(range)+min);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from a Gaussian distribution.  A user
	 * specified lower and upper bound is provided to ensure that the values are within a legal
	 * range.  A drawn value outside the allowed range will be set to the closest bound.
	 * 
	 * @param input Input image.  Modified.
	 * @param rand Random number generator
	 * @param mean Distribution's mean.
	 * @param sigma Distribution's standard deviation.
	 * @param lowerBound Lower bound of value clip
	 * @param upperBound Upper bound of value clip
	 */
	public static void fillGaussian(ImageInt16 input, Random rand , double mean , double sigma , int lowerBound , int upperBound ) {
		short[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (int)(rand.nextGaussian()*sigma+mean);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				data[index++] = (short)value;
			}
		}
	}

	/**
	 * Flips the image from top to bottom
	 */
	public static void flipVertical( ImageInt16 input ) {
		int h2 = input.height/2;

		for( int y = 0; y < h2; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = input.getStartIndex() + (input.height - y - 1) * input.getStride();

			int end = index1 + input.width;

			while( index1 < end ) {
				int tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2++] = (short)tmp;
			}
		}
	}

	/**
	 * Flips the image from left to right
	 */
	public static void flipHorizontal( ImageInt16 input ) {
		int w2 = input.width/2;

		for( int y = 0; y < input.height; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = index1 + input.width-1;

			int end = index1 + w2;

			while( index1 < end ) {
				int tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2--] = (short)tmp;
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the clockwise direction.
	 */
	public static void rotateCW( ImageInt16 input , ImageInt16 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int h = input.height-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(h-y,x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the counter-clockwise direction.
	 */
	public static void rotateCCW( ImageInt16 input , ImageInt16 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int w = input.width-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(y,w-x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Copies a rectangular region from one image into another.<br>
	 * output[dstX:(dstX+width) , dstY:(dstY+height-1)] = input[srcX:(srcX+width) , srcY:(srcY+height-1)]
	 *
	 * @param srcX x-coordinate of corner in input image
	 * @param srcY y-coordinate of corner in input image
	 * @param dstX x-coordinate of corner in output image
	 * @param dstY y-coordinate of corner in output image
	 * @param width Width of region to be copied
	 * @param height Height of region to be copied
	 * @param input Input image
	 * @param output output image
	 */
	public static void copy( int srcX , int srcY , int dstX , int dstY , int width , int height ,
							 ImageSInt32 input , ImageSInt32 output ) {

		if( input.width < srcX+width || input.height < srcY+height )
			throw new IllegalArgumentException("Copy region must be contained input image");
		if( output.width < dstX+width || output.height < dstY+height )
			throw new IllegalArgumentException("Copy region must be contained output image");

		for (int y = 0; y < height; y++) {
			int indexSrc = input.startIndex + (srcY + y) * input.stride + srcX;
			int indexDst = output.startIndex + (dstY + y) * output.stride + dstX;

			for (int x = 0; x < width; x++) {
				output.data[indexDst++] = input.data[indexSrc++];
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(ImageSInt32 input, int value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				input.data[index++] = value;
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(InterleavedS32 input, int value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			int end = index + input.width*input.numBands;
			for (; index < end; index++ ) {
				input.data[index] = value;
			}
		}
	}

	/**
	 * Fills the outside border with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 * @param radius Border width.   
	 */
	public static void fillBorder(ImageSInt32 input, int value, int radius ) {

		// top and bottom
		for (int y = 0; y < radius; y++) {
			int indexTop = input.startIndex + y * input.stride;
			int indexBottom = input.startIndex + (input.height-y-1) * input.stride;
			for (int x = 0; x < input.width; x++) {
				input.data[indexTop++] = value;
				input.data[indexBottom++] = value;
			}
		}

		// left and right
		int h = input.height-radius;
		int indexStart = input.startIndex + radius*input.stride;
		for (int x = 0; x < radius; x++) {
			int indexLeft = indexStart + x;
			int indexRight = indexStart + input.width-1-x;
			for (int y = radius; y < h; y++) {
				input.data[indexLeft] = value;
				input.data[indexRight] = value;
				
				indexLeft += input.stride;
				indexRight += input.stride;
			}
		}
	}

	/**
	 * Draws a filled rectangle that is aligned along the image axis inside the image.
	 *
	 * @param img Image the rectangle is drawn in.  Modified
	 * @param value Value of the rectangle
	 * @param x0 Top left x-coordinate
	 * @param y0 Top left y-coordinate
	 * @param width Rectangle width
	 * @param height Rectangle height
	 */
	public static void fillRectangle(ImageSInt32 img, int value, int x0, int y0, int width, int height) {
		int x1 = x0 + width;
		int y1 = y0 + height;

		for (int y = y0; y < y1; y++) {
			for (int x = x0; x < x1; x++) {
				if( img.isInBounds(x,y ))
					img.set(x, y, value);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(ImageSInt32 img, Random rand , int min , int max) {
		int range = max-min;

		int[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			for (int x = 0; x < img.width; x++) {
				data[index++] = (rand.nextInt(range)+min);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(InterleavedS32 img, Random rand , int min , int max) {
		int range = max-min;

		int[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			int end = index + img.width*img.numBands;
			for (; index <  end; index++) {
				data[index] = (rand.nextInt(range)+min);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from a Gaussian distribution.  A user
	 * specified lower and upper bound is provided to ensure that the values are within a legal
	 * range.  A drawn value outside the allowed range will be set to the closest bound.
	 * 
	 * @param input Input image.  Modified.
	 * @param rand Random number generator
	 * @param mean Distribution's mean.
	 * @param sigma Distribution's standard deviation.
	 * @param lowerBound Lower bound of value clip
	 * @param upperBound Upper bound of value clip
	 */
	public static void fillGaussian(ImageSInt32 input, Random rand , double mean , double sigma , int lowerBound , int upperBound ) {
		int[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (int)(rand.nextGaussian()*sigma+mean);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				data[index++] = value;
			}
		}
	}

	/**
	 * Flips the image from top to bottom
	 */
	public static void flipVertical( ImageSInt32 input ) {
		int h2 = input.height/2;

		for( int y = 0; y < h2; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = input.getStartIndex() + (input.height - y - 1) * input.getStride();

			int end = index1 + input.width;

			while( index1 < end ) {
				int tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2++] = (int)tmp;
			}
		}
	}

	/**
	 * Flips the image from left to right
	 */
	public static void flipHorizontal( ImageSInt32 input ) {
		int w2 = input.width/2;

		for( int y = 0; y < input.height; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = index1 + input.width-1;

			int end = index1 + w2;

			while( index1 < end ) {
				int tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2--] = (int)tmp;
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the clockwise direction.
	 */
	public static void rotateCW( ImageSInt32 input , ImageSInt32 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int h = input.height-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(h-y,x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the counter-clockwise direction.
	 */
	public static void rotateCCW( ImageSInt32 input , ImageSInt32 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int w = input.width-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(y,w-x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Copies a rectangular region from one image into another.<br>
	 * output[dstX:(dstX+width) , dstY:(dstY+height-1)] = input[srcX:(srcX+width) , srcY:(srcY+height-1)]
	 *
	 * @param srcX x-coordinate of corner in input image
	 * @param srcY y-coordinate of corner in input image
	 * @param dstX x-coordinate of corner in output image
	 * @param dstY y-coordinate of corner in output image
	 * @param width Width of region to be copied
	 * @param height Height of region to be copied
	 * @param input Input image
	 * @param output output image
	 */
	public static void copy( int srcX , int srcY , int dstX , int dstY , int width , int height ,
							 ImageSInt64 input , ImageSInt64 output ) {

		if( input.width < srcX+width || input.height < srcY+height )
			throw new IllegalArgumentException("Copy region must be contained input image");
		if( output.width < dstX+width || output.height < dstY+height )
			throw new IllegalArgumentException("Copy region must be contained output image");

		for (int y = 0; y < height; y++) {
			int indexSrc = input.startIndex + (srcY + y) * input.stride + srcX;
			int indexDst = output.startIndex + (dstY + y) * output.stride + dstX;

			for (int x = 0; x < width; x++) {
				output.data[indexDst++] = input.data[indexSrc++];
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(ImageSInt64 input, long value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				input.data[index++] = value;
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(InterleavedS64 input, long value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			int end = index + input.width*input.numBands;
			for (; index < end; index++ ) {
				input.data[index] = value;
			}
		}
	}

	/**
	 * Fills the outside border with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 * @param radius Border width.   
	 */
	public static void fillBorder(ImageSInt64 input, long value, int radius ) {

		// top and bottom
		for (int y = 0; y < radius; y++) {
			int indexTop = input.startIndex + y * input.stride;
			int indexBottom = input.startIndex + (input.height-y-1) * input.stride;
			for (int x = 0; x < input.width; x++) {
				input.data[indexTop++] = value;
				input.data[indexBottom++] = value;
			}
		}

		// left and right
		int h = input.height-radius;
		int indexStart = input.startIndex + radius*input.stride;
		for (int x = 0; x < radius; x++) {
			int indexLeft = indexStart + x;
			int indexRight = indexStart + input.width-1-x;
			for (int y = radius; y < h; y++) {
				input.data[indexLeft] = value;
				input.data[indexRight] = value;
				
				indexLeft += input.stride;
				indexRight += input.stride;
			}
		}
	}

	/**
	 * Draws a filled rectangle that is aligned along the image axis inside the image.
	 *
	 * @param img Image the rectangle is drawn in.  Modified
	 * @param value Value of the rectangle
	 * @param x0 Top left x-coordinate
	 * @param y0 Top left y-coordinate
	 * @param width Rectangle width
	 * @param height Rectangle height
	 */
	public static void fillRectangle(ImageSInt64 img, long value, int x0, int y0, int width, int height) {
		int x1 = x0 + width;
		int y1 = y0 + height;

		for (int y = y0; y < y1; y++) {
			for (int x = x0; x < x1; x++) {
				if( img.isInBounds(x,y ))
					img.set(x, y, value);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(ImageSInt64 img, Random rand , long min , long max) {
		long range = max-min;

		long[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			for (int x = 0; x < img.width; x++) {
				data[index++] = rand.nextInt((int)range)+min;
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, exclusive
	 */
	public static void fillUniform(InterleavedS64 img, Random rand , long min , long max) {
		long range = max-min;

		long[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			int end = index + img.width*img.numBands;
			for (; index <  end; index++) {
				data[index] = rand.nextInt((int)range)+min;
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from a Gaussian distribution.  A user
	 * specified lower and upper bound is provided to ensure that the values are within a legal
	 * range.  A drawn value outside the allowed range will be set to the closest bound.
	 * 
	 * @param input Input image.  Modified.
	 * @param rand Random number generator
	 * @param mean Distribution's mean.
	 * @param sigma Distribution's standard deviation.
	 * @param lowerBound Lower bound of value clip
	 * @param upperBound Upper bound of value clip
	 */
	public static void fillGaussian(ImageSInt64 input, Random rand , double mean , double sigma , long lowerBound , long upperBound ) {
		long[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				long value = (long)(rand.nextGaussian()*sigma+mean);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				data[index++] = value;
			}
		}
	}

	/**
	 * Flips the image from top to bottom
	 */
	public static void flipVertical( ImageSInt64 input ) {
		int h2 = input.height/2;

		for( int y = 0; y < h2; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = input.getStartIndex() + (input.height - y - 1) * input.getStride();

			int end = index1 + input.width;

			while( index1 < end ) {
				long tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2++] = (long)tmp;
			}
		}
	}

	/**
	 * Flips the image from left to right
	 */
	public static void flipHorizontal( ImageSInt64 input ) {
		int w2 = input.width/2;

		for( int y = 0; y < input.height; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = index1 + input.width-1;

			int end = index1 + w2;

			while( index1 < end ) {
				long tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2--] = (long)tmp;
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the clockwise direction.
	 */
	public static void rotateCW( ImageSInt64 input , ImageSInt64 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int h = input.height-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(h-y,x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the counter-clockwise direction.
	 */
	public static void rotateCCW( ImageSInt64 input , ImageSInt64 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int w = input.width-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(y,w-x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Copies a rectangular region from one image into another.<br>
	 * output[dstX:(dstX+width) , dstY:(dstY+height-1)] = input[srcX:(srcX+width) , srcY:(srcY+height-1)]
	 *
	 * @param srcX x-coordinate of corner in input image
	 * @param srcY y-coordinate of corner in input image
	 * @param dstX x-coordinate of corner in output image
	 * @param dstY y-coordinate of corner in output image
	 * @param width Width of region to be copied
	 * @param height Height of region to be copied
	 * @param input Input image
	 * @param output output image
	 */
	public static void copy( int srcX , int srcY , int dstX , int dstY , int width , int height ,
							 ImageFloat32 input , ImageFloat32 output ) {

		if( input.width < srcX+width || input.height < srcY+height )
			throw new IllegalArgumentException("Copy region must be contained input image");
		if( output.width < dstX+width || output.height < dstY+height )
			throw new IllegalArgumentException("Copy region must be contained output image");

		for (int y = 0; y < height; y++) {
			int indexSrc = input.startIndex + (srcY + y) * input.stride + srcX;
			int indexDst = output.startIndex + (dstY + y) * output.stride + dstX;

			for (int x = 0; x < width; x++) {
				output.data[indexDst++] = input.data[indexSrc++];
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(ImageFloat32 input, float value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				input.data[index++] = value;
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(InterleavedF32 input, float value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			int end = index + input.width*input.numBands;
			for (; index < end; index++ ) {
				input.data[index] = value;
			}
		}
	}

	/**
	 * Fills the outside border with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 * @param radius Border width.   
	 */
	public static void fillBorder(ImageFloat32 input, float value, int radius ) {

		// top and bottom
		for (int y = 0; y < radius; y++) {
			int indexTop = input.startIndex + y * input.stride;
			int indexBottom = input.startIndex + (input.height-y-1) * input.stride;
			for (int x = 0; x < input.width; x++) {
				input.data[indexTop++] = value;
				input.data[indexBottom++] = value;
			}
		}

		// left and right
		int h = input.height-radius;
		int indexStart = input.startIndex + radius*input.stride;
		for (int x = 0; x < radius; x++) {
			int indexLeft = indexStart + x;
			int indexRight = indexStart + input.width-1-x;
			for (int y = radius; y < h; y++) {
				input.data[indexLeft] = value;
				input.data[indexRight] = value;
				
				indexLeft += input.stride;
				indexRight += input.stride;
			}
		}
	}

	/**
	 * Draws a filled rectangle that is aligned along the image axis inside the image.
	 *
	 * @param img Image the rectangle is drawn in.  Modified
	 * @param value Value of the rectangle
	 * @param x0 Top left x-coordinate
	 * @param y0 Top left y-coordinate
	 * @param width Rectangle width
	 * @param height Rectangle height
	 */
	public static void fillRectangle(ImageFloat32 img, float value, int x0, int y0, int width, int height) {
		int x1 = x0 + width;
		int y1 = y0 + height;

		for (int y = y0; y < y1; y++) {
			for (int x = x0; x < x1; x++) {
				if( img.isInBounds(x,y ))
					img.set(x, y, value);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, inclusive
	 */
	public static void fillUniform(ImageFloat32 img, Random rand , float min , float max) {
		float range = max-min;

		float[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			for (int x = 0; x < img.width; x++) {
				data[index++] = rand.nextFloat()*range+min;
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, inclusive
	 */
	public static void fillUniform(InterleavedF32 img, Random rand , float min , float max) {
		float range = max-min;

		float[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			int end = index + img.width*img.numBands;
			for (; index <  end; index++) {
				data[index] = rand.nextFloat()*range+min;
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from a Gaussian distribution.  A user
	 * specified lower and upper bound is provided to ensure that the values are within a legal
	 * range.  A drawn value outside the allowed range will be set to the closest bound.
	 * 
	 * @param input Input image.  Modified.
	 * @param rand Random number generator
	 * @param mean Distribution's mean.
	 * @param sigma Distribution's standard deviation.
	 * @param lowerBound Lower bound of value clip
	 * @param upperBound Upper bound of value clip
	 */
	public static void fillGaussian(ImageFloat32 input, Random rand , double mean , double sigma , float lowerBound , float upperBound ) {
		float[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				float value = (float)(rand.nextGaussian()*sigma+mean);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				data[index++] = value;
			}
		}
	}

	/**
	 * Flips the image from top to bottom
	 */
	public static void flipVertical( ImageFloat32 input ) {
		int h2 = input.height/2;

		for( int y = 0; y < h2; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = input.getStartIndex() + (input.height - y - 1) * input.getStride();

			int end = index1 + input.width;

			while( index1 < end ) {
				float tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2++] = (float)tmp;
			}
		}
	}

	/**
	 * Flips the image from left to right
	 */
	public static void flipHorizontal( ImageFloat32 input ) {
		int w2 = input.width/2;

		for( int y = 0; y < input.height; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = index1 + input.width-1;

			int end = index1 + w2;

			while( index1 < end ) {
				float tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2--] = (float)tmp;
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the clockwise direction.
	 */
	public static void rotateCW( ImageFloat32 input , ImageFloat32 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int h = input.height-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(h-y,x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the counter-clockwise direction.
	 */
	public static void rotateCCW( ImageFloat32 input , ImageFloat32 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int w = input.width-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(y,w-x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Copies a rectangular region from one image into another.<br>
	 * output[dstX:(dstX+width) , dstY:(dstY+height-1)] = input[srcX:(srcX+width) , srcY:(srcY+height-1)]
	 *
	 * @param srcX x-coordinate of corner in input image
	 * @param srcY y-coordinate of corner in input image
	 * @param dstX x-coordinate of corner in output image
	 * @param dstY y-coordinate of corner in output image
	 * @param width Width of region to be copied
	 * @param height Height of region to be copied
	 * @param input Input image
	 * @param output output image
	 */
	public static void copy( int srcX , int srcY , int dstX , int dstY , int width , int height ,
							 ImageFloat64 input , ImageFloat64 output ) {

		if( input.width < srcX+width || input.height < srcY+height )
			throw new IllegalArgumentException("Copy region must be contained input image");
		if( output.width < dstX+width || output.height < dstY+height )
			throw new IllegalArgumentException("Copy region must be contained output image");

		for (int y = 0; y < height; y++) {
			int indexSrc = input.startIndex + (srcY + y) * input.stride + srcX;
			int indexDst = output.startIndex + (dstY + y) * output.stride + dstX;

			for (int x = 0; x < width; x++) {
				output.data[indexDst++] = input.data[indexSrc++];
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(ImageFloat64 input, double value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				input.data[index++] = value;
			}
		}
	}

	/**
	 * Fills the whole image with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 */
	public static void fill(InterleavedF64 input, double value) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			int end = index + input.width*input.numBands;
			for (; index < end; index++ ) {
				input.data[index] = value;
			}
		}
	}

	/**
	 * Fills the outside border with the specified value
	 *
	 * @param input An image.
	 * @param value The value that the image is being filled with.
	 * @param radius Border width.   
	 */
	public static void fillBorder(ImageFloat64 input, double value, int radius ) {

		// top and bottom
		for (int y = 0; y < radius; y++) {
			int indexTop = input.startIndex + y * input.stride;
			int indexBottom = input.startIndex + (input.height-y-1) * input.stride;
			for (int x = 0; x < input.width; x++) {
				input.data[indexTop++] = value;
				input.data[indexBottom++] = value;
			}
		}

		// left and right
		int h = input.height-radius;
		int indexStart = input.startIndex + radius*input.stride;
		for (int x = 0; x < radius; x++) {
			int indexLeft = indexStart + x;
			int indexRight = indexStart + input.width-1-x;
			for (int y = radius; y < h; y++) {
				input.data[indexLeft] = value;
				input.data[indexRight] = value;
				
				indexLeft += input.stride;
				indexRight += input.stride;
			}
		}
	}

	/**
	 * Draws a filled rectangle that is aligned along the image axis inside the image.
	 *
	 * @param img Image the rectangle is drawn in.  Modified
	 * @param value Value of the rectangle
	 * @param x0 Top left x-coordinate
	 * @param y0 Top left y-coordinate
	 * @param width Rectangle width
	 * @param height Rectangle height
	 */
	public static void fillRectangle(ImageFloat64 img, double value, int x0, int y0, int width, int height) {
		int x1 = x0 + width;
		int y1 = y0 + height;

		for (int y = y0; y < y1; y++) {
			for (int x = x0; x < x1; x++) {
				if( img.isInBounds(x,y ))
					img.set(x, y, value);
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, inclusive
	 */
	public static void fillUniform(ImageFloat64 img, Random rand , double min , double max) {
		double range = max-min;

		double[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			for (int x = 0; x < img.width; x++) {
				data[index++] = rand.nextDouble()*range+min;
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from an uniform distribution that has a range of min <= X < max.
	 *
	 * @param img Image which is to be filled.  Modified,
	 * @param rand Random number generator
	 * @param min Minimum value of the distribution, inclusive
	 * @param max Maximum value of the distribution, inclusive
	 */
	public static void fillUniform(InterleavedF64 img, Random rand , double min , double max) {
		double range = max-min;

		double[] data = img.data;

		for (int y = 0; y < img.height; y++) {
			int index = img.getStartIndex() + y * img.getStride();
			int end = index + img.width*img.numBands;
			for (; index <  end; index++) {
				data[index] = rand.nextDouble()*range+min;
			}
		}
	}

	/**
	 * Sets each value in the image to a value drawn from a Gaussian distribution.  A user
	 * specified lower and upper bound is provided to ensure that the values are within a legal
	 * range.  A drawn value outside the allowed range will be set to the closest bound.
	 * 
	 * @param input Input image.  Modified.
	 * @param rand Random number generator
	 * @param mean Distribution's mean.
	 * @param sigma Distribution's standard deviation.
	 * @param lowerBound Lower bound of value clip
	 * @param upperBound Upper bound of value clip
	 */
	public static void fillGaussian(ImageFloat64 input, Random rand , double mean , double sigma , double lowerBound , double upperBound ) {
		double[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				double value = (rand.nextGaussian()*sigma+mean);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				data[index++] = value;
			}
		}
	}

	/**
	 * Flips the image from top to bottom
	 */
	public static void flipVertical( ImageFloat64 input ) {
		int h2 = input.height/2;

		for( int y = 0; y < h2; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = input.getStartIndex() + (input.height - y - 1) * input.getStride();

			int end = index1 + input.width;

			while( index1 < end ) {
				double tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2++] = (double)tmp;
			}
		}
	}

	/**
	 * Flips the image from left to right
	 */
	public static void flipHorizontal( ImageFloat64 input ) {
		int w2 = input.width/2;

		for( int y = 0; y < input.height; y++ ) {
			int index1 = input.getStartIndex() + y * input.getStride();
			int index2 = index1 + input.width-1;

			int end = index1 + w2;

			while( index1 < end ) {
				double tmp = input.data[index1];
				input.data[index1++] = input.data[index2];
				input.data[index2--] = (double)tmp;
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the clockwise direction.
	 */
	public static void rotateCW( ImageFloat64 input , ImageFloat64 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int h = input.height-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(h-y,x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Rotates the image 90 degrees in the counter-clockwise direction.
	 */
	public static void rotateCCW( ImageFloat64 input , ImageFloat64 output ) {
		if( input.width != output.height || input.height != output.width )
			throw new IllegalArgumentException("Incompatible shapes");

		int w = input.width-1;

		for( int y = 0; y < input.height; y++ ) {
			int indexIn = input.startIndex + y*input.stride;
			for (int x = 0; x < input.width; x++) {
				output.unsafe_set(y,w-x,input.data[indexIn++]);
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageUInt8 input, Random rand , int min , int max) {
		int range = max-min;

		byte[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (data[index] & 0xFF) + rand.nextInt(range)+min;
				if( value < 0 ) value = 0;
				if( value > 255 ) value = 255;

				data[index++] = (byte) value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageUInt8 input, Random rand , double sigma , int lowerBound , int upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (input.data[index] & 0xFF) + (int)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] = (byte) value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageSInt8 input, Random rand , int min , int max) {
		int range = max-min;

		byte[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (data[index] ) + rand.nextInt(range)+min;
				if( value < -128 ) value = -128;
				if( value > 127 ) value = 127;

				data[index++] = (byte) value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageSInt8 input, Random rand , double sigma , int lowerBound , int upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (input.data[index] ) + (int)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] = (byte) value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageUInt16 input, Random rand , int min , int max) {
		int range = max-min;

		short[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (data[index] & 0xFFFF) + rand.nextInt(range)+min;
				if( value < 0 ) value = 0;
				if( value > 65535 ) value = 65535;

				data[index++] = (short) value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageUInt16 input, Random rand , double sigma , int lowerBound , int upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (input.data[index] & 0xFFFF) + (int)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] = (short) value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageSInt16 input, Random rand , int min , int max) {
		int range = max-min;

		short[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (data[index] ) + rand.nextInt(range)+min;
				if( value < -32768 ) value = -32768;
				if( value > 32767 ) value = 32767;

				data[index++] = (short) value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageSInt16 input, Random rand , double sigma , int lowerBound , int upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (input.data[index] ) + (int)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] = (short) value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageSInt32 input, Random rand , int min , int max) {
		int range = max-min;

		int[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (data[index] ) + rand.nextInt(range)+min;
				data[index++] =  value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageSInt32 input, Random rand , double sigma , int lowerBound , int upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				int value = (input.data[index] ) + (int)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] =  value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageSInt64 input, Random rand , long min , long max) {
		long range = max-min;

		long[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				long value = data[index] + rand.nextInt((int)range)+min;
				data[index++] =  value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageSInt64 input, Random rand , double sigma , long lowerBound , long upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				long value = (input.data[index] ) + (long)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] =  value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageFloat32 input, Random rand , float min , float max) {
		float range = max-min;

		float[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				float value = data[index] + rand.nextFloat()*range+min;
				data[index++] =  value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageFloat32 input, Random rand , double sigma , float lowerBound , float upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				float value = (input.data[index] ) + (float)(rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] =  value;
			}
		}
	}

	/**
	 * Adds uniform i.i.d noise to each pixel in the image.  Noise range is min <= X < max.
	 */
	public static void addUniform(ImageFloat64 input, Random rand , double min , double max) {
		double range = max-min;

		double[] data = input.data;

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				double value = data[index] + rand.nextDouble()*range+min;
				data[index++] =  value;
			}
		}
	}

	/**
	 * Adds Gaussian/normal i.i.d noise to each pixel in the image.  If a value exceeds the specified
	 * it will be set to the closest bound.
	 * @param input Input image.  Modified.
	 * @param rand Random number generator.
	 * @param sigma Distributions standard deviation.
	 * @param lowerBound Allowed lower bound
	 * @param upperBound Allowed upper bound
	 */
	public static void addGaussian(ImageFloat64 input, Random rand , double sigma , double lowerBound , double upperBound ) {

		for (int y = 0; y < input.height; y++) {
			int index = input.getStartIndex() + y * input.getStride();
			for (int x = 0; x < input.width; x++) {
				double value = (input.data[index] ) + (rand.nextGaussian()*sigma);
				if( value < lowerBound ) value = lowerBound;
				if( value > upperBound ) value = upperBound;
				input.data[index++] =  value;
			}
		}
	}

}
